package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"log/slog"
	"os"
	"time"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/json"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	"github.com/hertz-contrib/cors"
	"github.com/lotusdblabs/lotusdb/v2"
	"github.com/shengyanli1982/law"
	"gitlab.com/greyxor/slogor"
)

type Database struct {
	dbs *lotusdb.DB
}

func OpenDb(path string) (*Database, error) {
	options := lotusdb.DefaultOptions
	options.DirPath = path

	db, err := lotusdb.Open(options)
	if err != nil {
		return nil, err
	}
	return &Database{
		dbs: db,
	}, nil
}

func (d *Database) readHashMapFromDBs(key []byte) (map[string]any, error) {
	bytes, err := d.dbs.Get(key)
	if err != nil {
		return nil, err
	}
	var res map[string]any
	err = json.Unmarshal(bytes, &res)
	return res, err
}

func (d *Database) writeToDBs(key []byte, data any) error {
	bytes, err := json.Marshal(&data)
	if err != nil {
		return err
	}
	err = d.dbs.Put(key, bytes)
	return err
}

func (d *Database) readListFromDBs(key []byte) ([]any, error) {
	bytes, err := d.dbs.Get(key)
	if err != nil {
		return nil, err
	}
	var res []any
	err = json.Unmarshal(bytes, &res)
	return res, err
}

func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		// 路径存在
		return true, nil
	}
	if os.IsNotExist(err) {
		// 路径不存在
		return false, nil
	}
	// 发生其他错误
	return false, err
}

func main() {
	// 定义命令行参数
	hFlag := flag.String("h", "", "Server host")
	pFlag := flag.Int("p", 8080, "Port number")
	dFlag := flag.String("d", "data", "Database path")
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		fmt.Println("Please provide a file name as the first argument")
		os.Exit(1)
	}
	aw := law.NewWriteAsyncer(os.Stderr, nil)
	slog.SetDefault(slog.New(slogor.NewHandler(aw, &slogor.Options{
		TimeFormat: time.Stamp,
		Level:      slog.LevelDebug,
		ShowSource: true,
	})))
	defer aw.Stop()

	filename := args[0]
	host := *hFlag
	port := *pFlag
	path := *dFlag
	isDbInit, err := pathExists(path)
	if err != nil {
		log.Fatal(err)
	}
	data, err := os.ReadFile(filename)
	if err != nil {
		log.Fatal(err)
	}
	var routerMap map[string]any
	err = json.Unmarshal(data, &routerMap)
	dbs, err := OpenDb(path)
	h := server.Default(
		server.WithHostPorts(fmt.Sprintf("%s:%d", host, port)),
	)

	h.Use(cors.Default())
	for routeName, routeData := range routerMap {
		dbk := []byte(routeName)
		switch v := routeData.(type) {
		case map[string]any:
			if !isDbInit {
				dbs.writeToDBs(dbk, v)
			}
			h.GET("/"+routeName, func(ctx context.Context, c *app.RequestContext) {
				db, _ := dbs.readHashMapFromDBs(dbk)
				c.JSON(consts.StatusOK, db)
			})
			h.PUT("/"+routeName, func(ctx context.Context, c *app.RequestContext) {
				var tmp map[string]any
				err := c.Bind(&tmp)
				if err != nil {
					c.JSON(500, utils.H{
						"err": err.Error(),
					})
					return
				}
				dbs.writeToDBs(dbk, tmp)
				c.JSON(200, tmp)
			})
			h.PATCH("/"+routeName, func(ctx context.Context, c *app.RequestContext) {
				var tmp map[string]any
				err := c.Bind(&tmp)
				if err != nil {
					c.JSON(500, utils.H{
						"err": err.Error(),
					})
					return
				}
				db, _ := dbs.readHashMapFromDBs(dbk)
				for k, tmpv := range tmp {
					if dbv, ok := db[k]; ok && tmpv != dbv {
						db[k] = tmpv
					}
				}
				dbs.writeToDBs(dbk, db)
				c.JSON(200, db)
			})
		case []any:
			if !isDbInit {
				dbs.writeToDBs(dbk, v)
			}
			h.GET("/"+routeName, func(ctx context.Context, c *app.RequestContext) {
				db, _ := dbs.readListFromDBs(dbk)
				c.JSON(consts.StatusOK, db)
			})
			h.GET("/"+routeName+"/:id", func(ctx context.Context, c *app.RequestContext) {
				id := c.Param("id")
				db, _ := dbs.readListFromDBs(dbk)
				for _, dbv := range db {
					dbvId := fmt.Sprintf("%v", dbv.(map[string]any)["id"])
					if dbvId == id {
						c.JSON(200, dbv)
						return
					}
				}
				c.JSON(404, utils.H{
					"err": "Record not found",
				})
			})
			h.POST("/"+routeName, func(ctx context.Context, c *app.RequestContext) {
				var tmp map[string]any
				err := c.Bind(&tmp)
				if err != nil {
					c.JSON(500, utils.H{
						"err": err.Error(),
					})
					return
				}
				db, _ := dbs.readListFromDBs(dbk)
				db = append(db, tmp)
				dbs.writeToDBs(dbk, db)
				c.JSON(200, db)
			})
			h.PUT("/"+routeName+"/:id", func(ctx context.Context, c *app.RequestContext) {
				id := c.Param("id")
				db, _ := dbs.readListFromDBs(dbk)
				for idx, dbv := range db {
					dbvId := fmt.Sprintf("%v", dbv.(map[string]any)["id"])
					if dbvId == id {
						var tmp map[string]any
						err := c.Bind(&tmp)
						if err != nil {
							c.JSON(500, utils.H{
								"err": err.Error(),
							})
							return
						}
						db[idx] = tmp
						dbs.writeToDBs(dbk, db)
						c.JSON(200, db)
						return
					}
				}
				c.JSON(404, utils.H{
					"err": "Record not found",
				})
			})
			h.PATCH("/"+routeName+"/:id", func(ctx context.Context, c *app.RequestContext) {
				id := c.Param("id")
				db, _ := dbs.readListFromDBs(dbk)
				for idx, dbv := range db {
					dbvId := fmt.Sprintf("%v", dbv.(map[string]any)["id"])
					if dbvId == id {
						var tmp map[string]any
						err := c.Bind(&tmp)
						if err != nil {
							c.JSON(500, utils.H{
								"err": err.Error(),
							})
							return
						}
						newV := dbv.(map[string]any)
						for k, tmpv := range tmp {
							if newVV, ok := newV[k]; ok && tmpv != newVV {
								newV[k] = tmpv
							}
						}
						db[idx] = newV
						dbs.writeToDBs(dbk, db)
						c.JSON(200, db)
						return
					}
				}
				c.JSON(404, utils.H{
					"err": "Record not found",
				})
			})
			h.DELETE("/"+routeName+"/:id", func(ctx context.Context, c *app.RequestContext) {
				id := c.Param("id")
				db, _ := dbs.readListFromDBs(dbk)
				for idx, dbv := range db {
					dbvId := fmt.Sprintf("%v", dbv.(map[string]any)["id"])
					if dbvId == id {
						db = append(db[:idx], db[idx+1:]...)
						dbs.writeToDBs(dbk, db)
						c.JSON(200, db)
						return
					}
				}
				c.JSON(404, utils.H{
					"err": "Record not found",
				})
			})
		default:
		}
	}

	h.Spin()
}
